<?php
/**
 * Shopware 5
 * Copyright (c) shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 */

/**
 *
 * Shopware Application
 */
class Shopware_Plugins_Core_PostFilter_Bootstrap extends Shopware_Components_Plugin_Bootstrap
{
    protected static $baseFile;
    protected $basePathUrl = '';
    protected $basePath = '';
    protected $useSecure = false;
    protected $backLinkWhiteList = array();
    protected $urls;

    /**
     * Install filter plugin
     *
     * @return bool
     */
    public function install()
    {
        $this->subscribeEvent(
            'Enlight_Plugins_ViewRenderer_FilterRender',
            'onFilterRender'
        );
        return true;
    }

    /**
     * Plugin event method
     *
     * @param Enlight_Event_EventArgs $args
     * @return mixed
     */
    public function onFilterRender(Enlight_Event_EventArgs $args)
    {
        /** @var $request Enlight_Controller_Request_RequestHttp */
        $request = $args->getSubject()->Action()->Request();
        /** @var $response Enlight_Controller_Response_ResponseHttp */
        $response = $args->getSubject()->Action()->Response();

        $source = $args->getReturn();

        $headers = $response->getHeaders();
        foreach ($headers as $header) {
            if ($header['name'] == 'Content-Type' && strpos($header['value'], 'application/javascript') === 0) {
                $source = str_replace(array("\r\n", "\r"), "\n", $source);
                $expressions = array(
                    // Remove comments
                    '#/\*.*?\*/#ms' => '',
                    '#^\s*//.*$#m' => '',
                    //'#\n\s+#ms' => '',
                    '#^\s+#ms' => '',
                    //'#\s+$#ms' => '',
                );
                $source = preg_replace(array_keys($expressions), array_values($expressions), $source);
                return $source;
            }
        }
        if ($request->getModuleName() !== 'frontend' && $request->getModuleName() !== 'widgets') {
            return $args->getReturn();
        }
        $source = $this->filterUrls($source);

        return $this->filterSource($source);
    }

    protected function filterUrls($source)
    {
        /** @var $router \Shopware\Components\Routing\RouterInterface */
        $router = $this->get('router');
        $baseFile = preg_quote($router->getContext()->getBaseFile(), '#');
        $regex = '#<(a|form|iframe|link|img)[^<>]*(href|src|action)="(' . $baseFile . '[^"]*)".*>#Umsi';
        if (preg_match_all($regex, $source, $matches) > 0) {
            $urls = array_map('htmlspecialchars_decode', $matches[3]);
            $this->urls = array_combine($matches[3], $router->generateList($urls));
        }
        // Rewrite urls in rss and atom feeds
        $regex = '#<(guid|link|id)>(' . $baseFile . '[^<]*)</(guid|link|id)>#Umsi';
        if (preg_match_all($regex, $source, $matches) > 0) {
            $urls = array_map('htmlspecialchars_decode', $matches[2]);
            $urls = array_combine($matches[2], $router->generateList($urls));
            $source = preg_replace_callback($regex, function ($found) use (&$urls) {
                return '<' . $found[1] . '>' . $urls[$found[2]] . '</' . $found[3] . '>';
            }, $source);
        }
        return $source;
    }

    /**
     * Initializes plugin config
     */
    public function initConfig()
    {
        $shopConfig = Shopware()->Config();
        self::$baseFile = $shopConfig->baseFile;
        $this->useSecure = Shopware()->Front()->Request()->isSecure();

        $request = Shopware()->Front()->Request();
        $this->basePath = $request->getHttpHost() . $request->getBasePath() . '/';
        $this->basePathUrl = $request->getScheme() . '://' . $this->basePath;

        $this->backLinkWhiteList = preg_replace('#\s#', '', $shopConfig->seoBackLinkWhiteList);
        $this->backLinkWhiteList = explode(',', $this->backLinkWhiteList);

        $hosts = $this->getShopHosts();

        $this->backLinkWhiteList = array_merge(
            $this->backLinkWhiteList,
            array_map('trim', $hosts)
        );
    }

    /**
     * Filter html source
     *
     * @param string $source
     * @return string
     */
    public function &filterSource($source)
    {
        $source = preg_replace_callback('#<(a|form|iframe|link|img)[^<>]*(href|src|action)="([^"]*)".*>#Umsi', array($this, 'rewriteSrc'), $source);
        return $source;
    }

    /**
     * Rewrite source link
     *
     * @see \Shopware_Controllers_Backend_Newsletter::outputFilter
     * @param array $src
     * @return string
     */
    public function rewriteSrc($src)
    {
        if (!$this->basePath) {
            $this->initConfig();
        }

        if (empty($src[3])) {
            return $src[0];
        }

        if (!empty($this->backLinkWhiteList)) {
            if ($src[1] == 'a' && preg_match('#^https?://#', $src[3])) {
                $host = @parse_url($src[3], PHP_URL_HOST);
                if (!strstr($src[0], 'rel=') && !in_array($host, $this->backLinkWhiteList)) {
                    $src[0] = rtrim($src[0], '>') . ' rel="nofollow">';
                }
            }
        }

        $link = $src[3];
        switch ($src[1]) {
            case 'input':
            case 'img':
            case 'link':
            case 'script':
                if (strpos($src[3], self::$baseFile) === 0) {
                    $link = $this->rewriteLink($src[3]);
                }
                break;
            case 'form':
            case 'a':
                if (strpos($src[3], self::$baseFile) === 0) {
                    $link = $this->rewriteLink($src[3]);
                }
                break;
            default:
                break;
        }

        if (strpos($link, 'www.') === 0) {
            $link = 'http://' . $link;
        }
        if (!preg_match('#^[a-z]+:|^\#|^/#', $link)) {
            $link = $this->basePathUrl . $link;
        }

        //check canonical shopware configuration
        $forceCanonicalHttp = Shopware()->Config()->get('forceCanonicalHttp');

        //check if the current link is a canonical link
        $isCanonical = (
            strpos($src[0], 'rel="canonical"') !== false ||
            strpos($src[0], 'rel="prev"') !== false ||
            strpos($src[0], 'rel="next"') !== false
        );

        $replaceCanonical = !($isCanonical && $forceCanonicalHttp);

        if ($this->useSecure && $src[1] != 'a' && $replaceCanonical) {
            $link = str_replace('http://' . $this->basePath, 'https://' . $this->basePath, $link);
        }

        $src[0] = str_replace($src[2] . '="' . $src[3] . '"', $src[2] . '="' . $link . '"', $src[0]);
        return $src[0];
    }

    /**
     * Rewrite a link
     *
     * @param string $link
     * @return string
     */
    protected function rewriteLink($link = null)
    {
        return isset($this->urls[$link]) ? htmlspecialchars($this->urls[$link]) : $link;
    }

    /**
     * Returns plugin capabilities
     *
     * @return array
     */
    public function getCapabilities()
    {
        return array(
            'install' => false,
            'enable' => false,
            'update' => true
        );
    }

    /**
     * @return array
     * @throws Exception
     */
    private function getShopHosts()
    {
        $shop = $this->get('shop');
        if ($shop->getMain()) {
            $shop = $shop->getMain();
        }

        $shopHosts = $this->get('dbal_connection')->fetchAssoc(
            "SELECT host, hosts FROM s_core_shops WHERE id = :id",
            [':id' => $shop->getId()]
        );

        $hosts = [$shopHosts['host']];
        if (!empty($shopHosts['hosts'])) {
            $hosts = array_merge($hosts, explode("\n", $shopHosts['hosts']));
        }
        $hosts = array_filter($hosts);
        return $hosts;
    }
}
